/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.cst;

import java.util.HashMap;

import com.android.dx.rop.type.Type;

/**
 * Constants that represent an arbitrary type (reference or primitive).
 */
public final class CstType extends TypedConstant {

	/** {@code non-null;} map of interned types */
	private static final HashMap<Type, CstType> interns = new HashMap<Type, CstType>(
			100);

	/** {@code non-null;} instance corresponding to the class {@code Object} */
	public static final CstType OBJECT = intern(Type.OBJECT);

	/** {@code non-null;} instance corresponding to the class {@code Boolean} */
	public static final CstType BOOLEAN = intern(Type.BOOLEAN_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Byte} */
	public static final CstType BYTE = intern(Type.BYTE_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Character} */
	public static final CstType CHARACTER = intern(Type.CHARACTER_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Double} */
	public static final CstType DOUBLE = intern(Type.DOUBLE_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Float} */
	public static final CstType FLOAT = intern(Type.FLOAT_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Long} */
	public static final CstType LONG = intern(Type.LONG_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Integer} */
	public static final CstType INTEGER = intern(Type.INTEGER_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Short} */
	public static final CstType SHORT = intern(Type.SHORT_CLASS);

	/** {@code non-null;} instance corresponding to the class {@code Void} */
	public static final CstType VOID = intern(Type.VOID_CLASS);

	/** {@code non-null;} instance corresponding to the type {@code boolean[]} */
	public static final CstType BOOLEAN_ARRAY = intern(Type.BOOLEAN_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code byte[]} */
	public static final CstType BYTE_ARRAY = intern(Type.BYTE_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code char[]} */
	public static final CstType CHAR_ARRAY = intern(Type.CHAR_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code double[]} */
	public static final CstType DOUBLE_ARRAY = intern(Type.DOUBLE_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code float[]} */
	public static final CstType FLOAT_ARRAY = intern(Type.FLOAT_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code long[]} */
	public static final CstType LONG_ARRAY = intern(Type.LONG_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code int[]} */
	public static final CstType INT_ARRAY = intern(Type.INT_ARRAY);

	/** {@code non-null;} instance corresponding to the type {@code short[]} */
	public static final CstType SHORT_ARRAY = intern(Type.SHORT_ARRAY);

	/** {@code non-null;} the underlying type */
	private final Type type;

	/**
	 * {@code null-ok;} the type descriptor corresponding to this instance, if
	 * calculated
	 */
	private CstString descriptor;

	/**
	 * Returns an instance of this class that represents the wrapper class
	 * corresponding to a given primitive type. For example, if given
	 * {@link Type#INT}, this method returns the class reference
	 * {@code java.lang.Integer}.
	 * 
	 * @param primitiveType
	 *            {@code non-null;} the primitive type
	 * @return {@code non-null;} the corresponding wrapper class
	 */
	public static CstType forBoxedPrimitiveType(Type primitiveType) {
		switch (primitiveType.getBasicType()) {
		case Type.BT_BOOLEAN:
			return BOOLEAN;
		case Type.BT_BYTE:
			return BYTE;
		case Type.BT_CHAR:
			return CHARACTER;
		case Type.BT_DOUBLE:
			return DOUBLE;
		case Type.BT_FLOAT:
			return FLOAT;
		case Type.BT_INT:
			return INTEGER;
		case Type.BT_LONG:
			return LONG;
		case Type.BT_SHORT:
			return SHORT;
		case Type.BT_VOID:
			return VOID;
		}

		throw new IllegalArgumentException("not primitive: " + primitiveType);
	}

	/**
	 * Returns an interned instance of this class for the given type.
	 * 
	 * @param type
	 *            {@code non-null;} the underlying type
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static CstType intern(Type type) {
		synchronized (interns) {
			CstType cst = interns.get(type);

			if (cst == null) {
				cst = new CstType(type);
				interns.put(type, cst);
			}

			return cst;
		}
	}

	/**
	 * Constructs an instance.
	 * 
	 * @param type
	 *            {@code non-null;} the underlying type
	 */
	public CstType(Type type) {
		if (type == null) {
			throw new NullPointerException("type == null");
		}

		if (type == type.KNOWN_NULL) {
			throw new UnsupportedOperationException(
					"KNOWN_NULL is not representable");
		}

		this.type = type;
		this.descriptor = null;
	}

	/** {@inheritDoc} */
	@Override
	public boolean equals(Object other) {
		if (!(other instanceof CstType)) {
			return false;
		}

		return type == ((CstType) other).type;
	}

	/** {@inheritDoc} */
	@Override
	public int hashCode() {
		return type.hashCode();
	}

	/** {@inheritDoc} */
	@Override
	protected int compareTo0(Constant other) {
		String thisDescriptor = type.getDescriptor();
		String otherDescriptor = ((CstType) other).type.getDescriptor();
		return thisDescriptor.compareTo(otherDescriptor);
	}

	/** {@inheritDoc} */
	@Override
	public String toString() {
		return "type{" + toHuman() + '}';
	}

	/** {@inheritDoc} */
	public Type getType() {
		return Type.CLASS;
	}

	/** {@inheritDoc} */
	@Override
	public String typeName() {
		return "type";
	}

	/** {@inheritDoc} */
	@Override
	public boolean isCategory2() {
		return false;
	}

	/** {@inheritDoc} */
	public String toHuman() {
		return type.toHuman();
	}

	/**
	 * Gets the underlying type (as opposed to the type corresponding to this
	 * instance as a constant, which is always {@code Class}).
	 * 
	 * @return {@code non-null;} the type corresponding to the name
	 */
	public Type getClassType() {
		return type;
	}

	/**
	 * Gets the type descriptor for this instance.
	 * 
	 * @return {@code non-null;} the descriptor
	 */
	public CstString getDescriptor() {
		if (descriptor == null) {
			descriptor = new CstString(type.getDescriptor());
		}

		return descriptor;
	}
}
